﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Infraestructure.CrossCutting.Auditing;
using System.Data.Objects.DataClasses;
using System.Runtime.Serialization;
using System.IO;
using System.Data.Objects;
using System.Reflection;
using System.Xml.Serialization;
using System.Xml.Linq;
using System.Xml;
using System.Data;

namespace Infraestructure.CrossCutting.NetFramework.Auditing
{
    public sealed class AuditManager
        : IAuditManager
    {
        #region Private

        private  AuditBDEntities context;
        private string connectionString;

        #endregion

        #region Constructor

        public AuditManager()
        {
            if (string.IsNullOrEmpty(this.connectionString))
            {
                context = new AuditBDEntities();
            }
            else
            {
                context = new AuditBDEntities(this.connectionString);
            }
        }

        public AuditManager(string connectionString)
        {
            this.connectionString = connectionString;
            context = new AuditBDEntities(this.connectionString);
        }

        #endregion

        #region Inferface Implementation

        public string ConnectionString
        {
            get { return connectionString; }
            set { connectionString = value; }
        }

        public void InsertAudit(ObjectStateManager objectStateManager, Guid UserID)
        {

            List<Audit> auditList = new List<Auditing.Audit>();

            IEnumerable<ObjectStateEntry> changes = objectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified);
            foreach (ObjectStateEntry stateEntryEntity in changes)
            {
                if (!stateEntryEntity.IsRelationship && stateEntryEntity.Entity != null && !(stateEntryEntity.Entity is Audit))
                {
                    Audit audit = this.AuditTrailFactory(stateEntryEntity, UserID);
                    auditList.Add(audit);
                }
            }

            if (auditList.Count > 0)
            {
                foreach (Auditing.Audit audit in auditList)
                {
                    context.AddToAudits(audit);
                }
                context.AcceptAllChanges();
            }
        }

        #endregion

        #region Private Members

        private Audit AuditTrailFactory(ObjectStateEntry entry, Guid UserID)
        {
            Audit audit = new Audit();
            audit.AuditID = Guid.NewGuid();
            audit.RevisionStamp = DateTime.Now;
            audit.TableName = entry.EntitySet.Name;
            audit.UserID = UserID;

            if (entry.State == EntityState.Added)
            {//entry is Added 
                audit.NewData = GetEntryValueInString(entry, false);
                audit.Action =  Auditing.Actions.INSERT.ToString();
            }
            else if (entry.State == EntityState.Deleted)
            {//entry in deleted
                audit.OldData = GetEntryValueInString(entry, true);
                audit.Action = Auditing.Actions.DELETE.ToString();
            }
            else
            {//entry is modified
                audit.OldData = GetEntryValueInString(entry, true);
                audit.NewData = GetEntryValueInString(entry, false);
                audit.Action = Auditing.Actions.UPDATE.ToString();

                IEnumerable<string> modifiedProperties = entry.GetModifiedProperties();
                //passing collection of mismatched Columns name as serialized string 
                audit.ChangedColumns = XMLSerializationHelper.XmlSerialize(modifiedProperties.ToArray());
            }

            return audit;
        }

        private EntityObject CloneEntity(EntityObject obj)
        {
            DataContractSerializer dcSer = new DataContractSerializer(obj.GetType());
            MemoryStream memoryStream = new MemoryStream();

            dcSer.WriteObject(memoryStream, obj);
            memoryStream.Position = 0;

            EntityObject newObject = (EntityObject)dcSer.ReadObject(memoryStream);
            return newObject;
        }

        private string GetEntryValueInString(ObjectStateEntry entry, bool isOrginal)
        {
            if (entry.Entity is EntityObject)
            {
                object target = CloneEntity((EntityObject)entry.Entity);
                foreach (string propName in entry.GetModifiedProperties())
                {
                    object setterValue = null;
                    if (isOrginal)
                    {
                        setterValue = entry.OriginalValues[propName];
                    }
                    else
                    {
                        setterValue = entry.CurrentValues[propName];
                    }
                    PropertyInfo propInfo = target.GetType().GetProperty(propName);
                    if (setterValue == DBNull.Value)
                    {
                        setterValue = null;
                    }
                    propInfo.SetValue(target, setterValue, null);
                }

                XmlSerializer formatter = new XmlSerializer(target.GetType());
                XDocument document = new XDocument();

                using (XmlWriter xmlWriter = document.CreateWriter())
                {
                    formatter.Serialize(xmlWriter, target);
                }
                return document.Root.ToString();
            }
            return null;
        }

        #endregion
    }
}
